/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkArrayRef, ArkAssignStmt, ArkFile, AstTreeUtils, ClassType, Local, ts, UnclearReferenceType } from "arkanalyzer";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { FileMatcher, MatcherCallback, MatcherTypes } from "../../matcher/Matchers";
import { Defects, IssueReport } from "../../model/Defects";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { BaseChecker, BaseMetaData } from "../BaseChecker";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoUnsafeAssignmentCheck');
const gMetaData: BaseMetaData = {
  severity: 2,
  ruleDocPath: "docs/no-unsafe-assignment-check.md",
  description: "Disallow assigning a value with type `any` to variables and properties.",
};

export class NoUnsafeAssignmentCheck implements BaseChecker {
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];
  readonly metaData: BaseMetaData = gMetaData;
  public rule: Rule;
  private fileMatcher: FileMatcher = {
    matcherType: MatcherTypes.FILE,
  };

  public registerMatchers(): MatcherCallback[] {
    const fileMatcherCb: MatcherCallback = {
      matcher: this.fileMatcher,
      callback: this.check
    }
    return [fileMatcherCb];
  }

  public check = (target: ArkFile) => {
    const severity = this.rule.alert ?? this.metaData.severity;
    const sourceFile: ts.SourceFile = AstTreeUtils.getASTNode(target.getName(), target.getCode());
    const visitNode = (node: ts.Node) => {
      // 检查变量声明
      if (ts.isVariableDeclaration(node)) {
        if (!this.checkAssignment(node.name, node.initializer, sourceFile)){
          const pos = this.getLineAndColumn(node);
          const message = '`Unsafe assignment of an any value.`';
          this.addIssueReport(pos, severity, message);
          return;
        }
      }
      // 检查赋值表达式
      else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
        if(!this.checkAssignment(node.left, node.right, sourceFile)){
          const pos = this.getLineAndColumn(node);
          const message = '`Unsafe assignment of an any value.`';
          this.addIssueReport(pos, severity, message);
          return;
        }
      }
      // 检查函数参数默认值
      else if (ts.isParameter(node) && node.initializer) {
        if (node.initializer.getText().includes('as any')) {
          const pos = this.getLineAndColumn(node);
          const message = '`Unsafe assignment of an any value.`';
          this.addIssueReport(pos, severity, message);
          return;
        }
      }
      // 检查类属性初始化
      else if (ts.isPropertyDeclaration(node) && node.initializer) {
        if (node.initializer.getText().includes('as any')) {
          const pos = this.getLineAndColumn(node);
          const message = '`Unsafe assignment of an any value.`';
          this.addIssueReport(pos, severity, message);
          return;
        }
      }
      // 检查属性赋值
      else if (ts.isPropertyAssignment(node) && node.initializer) {
        if(!this.checkAssignment(node.name, node.initializer, sourceFile)){
          const pos = this.getLineAndColumn(node);
          const message = '`Unsafe assignment of an any value.`';
          this.addIssueReport(pos, severity, message);
          return;
        }
      }
      else if (node.kind === ts.SyntaxKind.AnyKeyword) {
        if (this.isInEqualsSign(node)) {
          const pos = this.getLineAndColumn(node);
          const message = '`Unsafe assignment of an any value.`';
          this.addIssueReport(pos, severity, message);
        }
      }

      // 递归遍历子节点
      ts.forEachChild(node, visitNode);
    }
    ts.forEachChild(sourceFile, visitNode);
  }

  /**
   * 检查节点的父节点是否为赋值语句
   */
  private isInEqualsSign(node: ts.Node): boolean {
    let mNode = node;
    while(mNode) {
      if (ts.isVariableDeclaration(mNode) || (ts.isBinaryExpression(mNode) && mNode.operatorToken.kind === ts.SyntaxKind.EqualsToken)) {
        return true;
      }
      if (ts.isSourceFile(mNode)) {
        return false;
      }
      mNode = mNode.parent;
    }
    return false;
  }

  /**
   * 检查赋值操作是否安全
   * @param left 赋值目标的节点
   * @param right 赋值值的节点
   * @returns 是否安全 true安全，false不安全
   */
  private checkAssignment(left: ts.Node, right: ts.Node | undefined, sourceFile: ts.SourceFile): boolean {
    if (!right) return true;

    try {
      // 获取右侧值的类型
      const typeChecker = this.createTypeChecker(sourceFile);
      const type = typeChecker.getTypeAtLocation(right);
      const typeArguments = typeChecker.getTypeArguments(type as ts.TypeReference);
      if (typeArguments.some(item => item.flags & ts.TypeFlags.Any)) {
        return false;
      }
      // 如果右侧类型是 `any` 或未知类型，则输出警告
      else if (type?.flags & ts.TypeFlags.Any || type?.flags & ts.TypeFlags.Unknown) {
        return false;
      } else if (this.isTypeReferenceHasAny(type, typeChecker)) {
        return false;
      }
    } catch(error) {
      logger.error(left.getText(sourceFile) + " = " + right.getText(sourceFile) + "\ntype检查失败：\n" + error);
    }
    return true;
  }

  private isTypeReferenceHasAny(type: ts.Type, typeChecker: ts.TypeChecker): boolean {
    let mType = type;
    let isHas = false;
    while(mType) {
      const typeArguments = typeChecker.getTypeArguments(mType as ts.TypeReference);
      if (typeArguments.length > 0) {
        typeArguments.forEach((item: ts.Type)=>{
          mType = item
          if(item.flags & ts.TypeFlags.Any) {
            isHas = true;
            return;
          }
        });
      } else {
        return isHas;
      }

      if (isHas) return isHas;
    }
    return isHas;
  }

  /**
   * 创建 TypeChecker 对象
   * @param sourceFile 要检查的 SourceFile
   */
  private createTypeChecker(sourceFile: ts.SourceFile): ts.TypeChecker {
    const program = ts.createProgram([sourceFile.fileName], {
      target: ts.ScriptTarget.Latest,
      strict: true,
      noImplicitAny: true
    });
    return program.getTypeChecker();
  }

  private getMessage(stmt: ArkAssignStmt): string {
    let leftType = '';
    let rightType = '';
    let leftOp = stmt.getLeftOp();
    if (leftOp instanceof UnclearReferenceType) {
      leftType = leftOp.getType().toString();
    } else if (leftOp instanceof Local) {
      leftType = leftOp.getType().toString();
    }

    let rightOp = stmt.getRightOp();
    if (rightOp instanceof ArkArrayRef) {
      rightType = rightOp.getBase().getType().toString();
    } else if (rightOp instanceof Local) {
      let rType1 = rightOp.getType();
      if (rType1 instanceof ClassType) {
        rightType = `${rType1.getClassSignature().getClassName()}<${rType1.getRealGenericTypes()?.map(t => t.toString()).join(', ')}>`;
      }
    }
    if (leftType.length > 0 && rightType.length > 0) {
      return `Unsafe assignment of type ${rightType} to a variable of type ${leftType}.`
    }
    return `Unsafe assignment of an any value.`;
  }

  private addIssueReport(pos: { line: number, startCol: number, filePath: string }, severity: number, message: string) {
    const fixKey = pos.line + '%' + pos.startCol + '%' + this.rule.ruleId;
    const mergeKey = pos.filePath + '%' + fixKey + '%' + message;
    let defects = new Defects(pos.line, pos.startCol, message, severity, this.rule.ruleId,
      mergeKey, this.metaData.ruleDocPath, true, false, false, fixKey);
    this.issues.push(new IssueReport(defects, undefined));
    RuleListUtil.push(defects);
  }
  
  private getLineAndColumn(node: ts.Node) {
    const sourceFile = node.getSourceFile();
    let start = node.getStart();
    if (node.modifiers) {
      start = node.modifiers[0].end + 1;
    }
    if (ts.isVariableDeclaration(node) && node.initializer) {
      if (node.initializer.kind === ts.SyntaxKind.ArrayLiteralExpression) {
        node.initializer.forEachChild((item: ts.Node)=>{
          if (item.kind === ts.SyntaxKind.SpreadElement) {
            start = item.getStart();
            return;
          }
        })
      }
    }
    const { line: textLine, character: textCharacter } = sourceFile.getLineAndCharacterOfPosition(start);
    return {
      line: textLine + 1, // 行号从1开始
      startCol: textCharacter + 1, // 列号从1开始
      filePath: sourceFile.fileName
    };
  }
}
